package jmind.core.http;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;

import jmind.base.lang.Pair;
import jmind.base.util.AppEnvironment;
import jmind.base.util.DataUtil;
import jmind.base.util.GlobalConstants;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

/**
 * 使用连接池
 * http://niuzhenxin.iteye.com/blog/2100100
 * https://www.cnblogs.com/softidea/p/5683083.html
 * @author xieweibo
 * @date 2015年7月21日
 */
public class PoolingHttpClient4 extends HttpClient4 {

    final CloseableHttpClient httpclient;

    public PoolingHttpClient4() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {

        SSLContext sslCtx = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return true;
            }
        }).build();

        LayeredConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(sslCtx);

        //        LayeredConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(SSLContexts.createDefault(), new DefaultHostnameVerifier(
        //                PublicSuffixMatcherLoader.getDefault()));

        //  LayeredConnectionSocketFactory sslcsf = SSLConnectionSocketFactory.getSocketFactory();

        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslcsf).build();

        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        // PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        // 将最大连接数增加到500
        cm.setMaxTotal(500);
        // 将每个路由基础的连接增加到100
        cm.setDefaultMaxPerRoute(100);
        // 链接超时setConnectTimeout ，读取超时setSocketTimeout
        int connectTimeout= DataUtil.toInt(AppEnvironment.getProperty("connectTimeout","2000"));
        int socketTimeout=DataUtil.toInt(AppEnvironment.getProperty("socketTimeout","2000"));

        RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout)
                .build();
        httpclient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(defaultRequestConfig)
                .build();

        new IdleConnectionMonitorThread(cm).start();
    }

    /**
     * 
     * @param url
     * @param code 编码默认 UTF-8
     * @param soTimeout 超时时间
     * @return
     */
    public String get(String url, final String code, int soTimeout,Map<String,String> headers) {
        long start = System.currentTimeMillis();
        String res = null;
        HttpGet httpget = new HttpGet(url);
        if(!DataUtil.isEmpty(headers)){
            headers.forEach((k,v)->{
                httpget.setHeader(k,v);
            });
        }
        //httpget.setHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
        try {
            ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
                public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {
                    int status = response.getStatusLine().getStatusCode();
                    if (status >= 200 && status < 300) {
                        HttpEntity entity = response.getEntity();
                        return entity != null ? EntityUtils.toString(entity, code) : null;
                    } else {
                        httpget.abort();
                        throw new ClientProtocolException("Unexpected response status: " + status);
                    }
                }

            };
            res = httpclient.execute(httpget, responseHandler);
            success(start, url, res);
        } catch (Exception e) {
            httpget.abort();
            fail(start, url, e);
        }finally {
            httpget.releaseConnection();
        }
        return res;
    }




    public String post(String url, String code, int timeout, List<NameValuePair> params) {
        long start = System.currentTimeMillis();
        String res = null;
        CloseableHttpResponse response = null;
        HttpPost httpPost = new HttpPost(url);
        try {
            if (params != null)
                httpPost.setEntity(new UrlEncodedFormEntity(params, code));
            response = httpclient.execute(httpPost);
            HttpEntity entity2 = response.getEntity();
            res = EntityUtils.toString(entity2, code);
            EntityUtils.consume(entity2);
            success(start, url + "?" + params, res);
        } catch (Exception e) {
            httpPost.abort();
            fail(start, url + "?" + params, e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                }
            }
            if(httpPost!=null) {
                httpPost.releaseConnection();
            }

        }
        return res;
    }



    /**
     * 上传文件
     * http://blog.csdn.net/x931100537/article/details/39295221
     * @param url 请求url
     * @param params 参数
     * @param fileName file参数名
     * @param file 文件
     * @return
     */
    public String postFile(String url, Map<String, ?> params, String fileName, File file, String code) {
        Charset charset = DataUtil.isEmpty(code) ? Charset.defaultCharset() : Charset.forName(code);
        long start = System.currentTimeMillis();
        CloseableHttpResponse response = null;
        String res = null;
        try {
            HttpPost httppost = new HttpPost(url);

            //  setMode 解决文件名是中文。乱码问题
            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()
                    .setMode(HttpMultipartMode.BROWSER_COMPATIBLE).setCharset(charset);
            multipartEntityBuilder.addBinaryBody(fileName, file);

            //           FileBody bin = new FileBody(file, ContentType.create("application/octet-stream", Consts.UTF_8),  file.getName());
            //           multipartEntityBuilder.addPart(fileName, bin);

            if (!DataUtil.isEmpty(params)) {
                ContentType contentType = ContentType.create("text/plain", charset);
                for (Entry<String, ?> entry : params.entrySet()) {
                    multipartEntityBuilder.addTextBody(entry.getKey(), entry.getValue().toString(), contentType);
                }
            }

            httppost.setEntity(multipartEntityBuilder.build());
            response = httpclient.execute(httppost);
            HttpEntity resEntity = response.getEntity();
            if (resEntity != null) {
                res = EntityUtils.toString(resEntity, charset);
                EntityUtils.consume(resEntity);
            }
            httppost.releaseConnection();
            success(start, url, res);
        } catch (Exception e) {
            fail(start, url, e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                }
            }

        }
        return res;
    }

    @Override
    public String postFile(String url, Map<String, ?> params, List<Pair<String, File>> files, String code) {
        Charset charset = DataUtil.isEmpty(code) ? Charset.defaultCharset() : Charset.forName(code);
        long start = System.currentTimeMillis();
        String res = null;
        CloseableHttpResponse response = null;
        try {
            HttpPost httppost = new HttpPost(url);

            //  setMode 解决文件名是中文。乱码问题
            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()
                    .setMode(HttpMultipartMode.BROWSER_COMPATIBLE).setCharset(charset);
            for (Pair<String, File> file : files) {
                multipartEntityBuilder.addBinaryBody(file.getFirst(), file.getSecond());
            }

            //           FileBody bin = new FileBody(file, ContentType.create("application/octet-stream", Consts.UTF_8),  file.getName());
            //           multipartEntityBuilder.addPart(fileName, bin);

            if (!DataUtil.isEmpty(params)) {
                ContentType contentType = ContentType.create("text/plain", charset);
                for (Entry<String, ?> entry : params.entrySet()) {
                    multipartEntityBuilder.addTextBody(entry.getKey(), entry.getValue().toString(), contentType);
                }
            }

            httppost.setEntity(multipartEntityBuilder.build());
            response = httpclient.execute(httppost);
            HttpEntity resEntity = response.getEntity();
            if (resEntity != null) {
                res = EntityUtils.toString(resEntity, charset);
                EntityUtils.consume(resEntity);
            }
            httppost.releaseConnection();
            success(start, url, res);
        } catch (Exception e) {
            fail(start, url, e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                }
            }

        }
        return res;
    }

    @Override
    public String postBody(String url, String body,Map<String, String> headers) {
        long start = System.currentTimeMillis();
        String res = null;
        CloseableHttpResponse response = null;
        try {
            HttpPost httppost = new HttpPost(url);
            if(!DataUtil.isEmpty(headers)){
                headers.forEach((k,v)->{
                    httppost.addHeader(k,v);
                });
            }
            if (!DataUtil.isEmpty(body)) {
                httppost.setEntity(new StringEntity(body, GlobalConstants.CHARSET_UTF8));
            }
            response = httpclient.execute(httppost);
            HttpEntity resEntity = response.getEntity();
            if (resEntity != null) {
                res = EntityUtils.toString(resEntity, GlobalConstants.CHARSET_UTF8);
                EntityUtils.consume(resEntity);
            }
            httppost.releaseConnection();
            success(start, url, res);
        } catch (Exception e) {
            fail(start, url, e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                }
            }

        }
        return res;
    }

    // 监控有异常的链接
    public static class IdleConnectionMonitorThread extends Thread {

        private final HttpClientConnectionManager connMgr;
        private volatile boolean shutdown;

        public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
            super();
            this.connMgr = connMgr;
        }

        @Override
        public void run() {
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(180000);
                        // 关闭失效的连接
                        connMgr.closeExpiredConnections();
                        // 可选的, 关闭30秒内不活动的连接
                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                    }
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }

        public void shutdown() {
            shutdown = true;
            synchronized (this) {
                notifyAll();
            }
        }

    }

}
